<?php

namespace App\Models;

use App\Providers\LengthAwarePaginatorProvider;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Closure;
use DateTimeInterface;
use Illuminate\Database\Eloquent\Collection;
use PHPUnit\Framework\Constraint\Callback;
use Illuminate\Support\Str;

/**
 * 基础model
 *      php artisan make:model xx/Xxx --controller --api        // 表派生的控制器
 *      php artisan make:model Xxx --pivot                      // 创建中间表
 * 
 *
 * @date 2023-03-18
 */
abstract class BaseModel extends \Illuminate\Database\Eloquent\Model {


    public function __construct($attributes = []) {
        $this->table = get_called_class()::Table;       // 减少变量的定义, 清爽代码
        $this->primaryKey = get_called_class()::PrimaryKey;
        parent::__construct($attributes);
    }
    
    // protected $connection = "yunyan";                // 自定义数据库连接
    // use SoftDeletes;                                 // 启用软删除 表字段deleted_at datetime
    const Table = 'base_models';
    // protected $table = self::Table;                  // 表名的设置 不是必须 但是laravel会默认给模型相关表加复数s  tests
    const PrimaryKey = "u_id";
    // protected $primaryKey = self::PrimaryKey;        // 主键设置     不支持复合主键, 建议多列唯一索引
    // protected $keyType = "string";                   // 主键的类型
    // public $incrementing = true;                     // 主键是否自增
    // protected $attributes = ['password'=>'123456'];  // 定义属性字段的默认值
    // protected $casts = ['is_admin' => 'boolean'];    // 类型转换
    protected $fillable = [];                           // 字段放行 允许对 name 和 email 字段操作
    // protected $guarded = [];                         // 字段拦截, 为空所有属性都可以批量赋值, 不可同时设定$fillable $guarded
    const CREATED_AT = 'create_time';                   // 自定义时间戳字段名
    const UPDATED_AT = 'update_time';
    public $timestamps = true;                          // 主动维护时间戳  timestamp=now()
    // protected $dateFormat = 'U';                     // 时间戳格式设置
    // protected $with = ['roles'];                     // 默认预加载关联模型    without(剔除预加载模型) withOnly(覆盖预加载(使失效)) load(延迟预加载) loadMissing(进一步优化)
    // protected $touches = ['post'];                   // save时更新父级belongsTo、belongsToMany的updated_at

    // protected $hidden = [];                          // 隐藏属性
    // protected $visible = ['ctime'];                  // model只显示的属性, 运行时修改属性makeVisible、makeHidden

    protected $appends = [];                            // 追加自定义属性访问器(Attribute->get), 运行时追加append('') setAppends([])

    protected function serializeDate(DateTimeInterface $date): string { return $date->format('Y-m-d H:i:s'); }
    // protected function createTime(): Attribute { return Attribute::make(get: fn($v) => $v ? date('Y-m-d H:i:s', strtotime($v)) : null); }
    // protected function updateTime(): Attribute { return Attribute::make(get: fn($v) => $v ? date('Y-m-d H:i:s', strtotime($v)) : null); }
    // protected function createdAt(): Attribute { return Attribute::make(get: fn($v) => $v ? date('Y-m-d H:i:s', strtotime($v)) : null); }
    // protected function updatedAt(): Attribute { return Attribute::make(get: fn($v) => $v ? date('Y-m-d H:i:s', strtotime($v)) : null); }
    // protected function deletedAt(): Attribute { return Attribute::make(get: fn($v) => $v ? date('Y-m-d H:i:s', strtotime($v)) : null); }

    /** 获取主键值 */
    public function getId() { $id = static::PrimaryKey; return $this->$id; }
    
    /** ========== 复用where ========== */
    /**
     * 复用where, 搭配when 常用于列表条件筛选
     *  static::$Wheres['dict_code']    = fn(Builder $query, ?string $value) => $query->where('dict_code', '=', $value);
     *  static::$Wheres['dict_label']   = fn($q, $v) => $q->where(atn('dict_label'), 'like', "%{$v}%");
     * query的方法 
     * @see \Illuminate\Database\Query\Builder
     */
    static $Wheres = [
        // 'id',    // 直接定义column, 默认where(column, '=', value)
    ];
    // 复用where
    public function scopeWheres(Builder $query, ?array $form, string ...$columns): void {
        foreach($columns as $column) {
            // 兼容key 驼峰/蛇形命名
            $value = get_value($form, $column);
            $query->when($value, fn($q) => in_array($column, static::$Wheres) ? $q->where(atn($column, static::Table), '=', $value) : static::$Wheres[$column]($q, $value));
        }
    }
    /** ========== 复用where ========== */


    /** ========== 增强when ========== */
    /**
     * 快速筛选出更新的值
     *
     * @param array $form 表单数据 
     * @param string|array ...$columns 待筛选的字段 column|[column, 指定key]
     * @return array 待更新的数据
     * @date 2023-10-10
     */
    protected static function when(array $form, string|array ...$columns): array {
        $values = [];
        foreach($columns as $column) {
            $value = get_value($form, is_array($column) ? $column[1] : $column);
            if ($value != null) {
                $values[is_array($column) ? $column[0] : $column] = $value;
            }
        }
        return $values;
    }
    /** ========== 增强when ========== */


    /** ========== 添加表名前缀 ========== */
    public static function atn(array|string $column) { return static::addTableName($column); }
    /** 批量給表字段添加表名前缀 */
    public static function addTableName(array|string $column): array|string {
        return atn($column, static::Table);
    }
    /** ========== 添加表名前缀 ========== */


    // 基于alibabaJava开发手册 https://github.com/alibaba/p3c
    /**
        分层领域模型规约：
        DO（ Data Object）：与数据库表结构一一对应，通过DAO层向上传输数据源对象。
        DTO（ Data Transfer Object）：数据传输对象，Service或Manager向外传输的对象。
        BO（ Business Object）：业务对象。 由Service层输出的封装业务逻辑的对象。
        AO（ Application Object）：应用对象。 在Web层与Service层之间抽象的复用对象模型，极为贴近展示层，复用度不高。
        VO（ View Object）：显示层对象，通常是Web向模板渲染引擎层传输的对象。
        POJO（ Plain Ordinary Java Object）：在本手册中， POJO专指只有setter/getter/toString的简单类，包括DO/DTO/BO/VO等。
        Query：数据查询对象，各层接收上层的查询请求。 注意超过2个参数的查询封装，禁止使用Map类来传输。
        领域模型命名规约：
        数据对象：xxxDO，xxx即为数据表名。
        数据传输对象：xxxDTO，xxx为业务领域相关的名称。
        展示对象：xxxVO，xxx一般为网页名称。
        POJO是DO/DTO/BO/VO的统称，禁止命名成xxxPOJO。
     */

     static $Vo = ['*', ];       // 后端
     static $Voi = ['*', ];      // 前端api

     public function scopeSelectVo(Builder $query): void {
        $query->select(atn(static::is_api() ? static::$Voi : static::$Vo, static::Table));
     }

    // =========== 单表单个直取操作 ===========
    public static function is_api() { return request()->is('api/*'); }
    public static function row(int|array $idOrWhere=[], Closure $query=null): ?BaseModel {
        $base_model = new static;
        $where = is_array($idOrWhere) ? $idOrWhere : [[$base_model->primaryKey, '=', $idOrWhere]];
        $model = $base_model->select(static::is_api() ? static::$Voi : static::$Vo)->where($where);
        if ($query) { $model = $query($model); }                                   // 匿名函数条件覆盖以上条件, where可=[]
        return $model->first();
    }
    public static function rows(array $where=[], ?Closure $query=null): Collection {
        $base_model = new static;
        $model = $base_model->select(static::is_api() ? static::$Voi : static::$Vo)->where($where);
        if ($query) { $model = $query($model); }                              // 匿名函数条件覆盖以上条件, where可=[]
        else { $model = $model->orderByDesc($base_model->primaryKey); }       // 默认id倒序, 后新增优先
        return $model->get();
    }
    public static function rows_pag(int|array $pag=1, array $where=[], ?Closure $query=null): LengthAwarePaginatorProvider {
        $base_model = new static;
        $page = is_int($pag) ? $pag : ($pag['page'] ?? 1);
        $per_page = $pag['per_page'] ?? $pag['page_size'] ?? $base_model->perPage;
        $model = $base_model->select(static::is_api() ? static::$Voi : static::$Vo)->where($where);
        if ($query) { $model = $query($model); }                              // 匿名函数条件覆盖以上条件, where可=[]
        else { $model = $model->orderByDesc($base_model->primaryKey); }       // 默认id倒序, 后新增优先
        return $model->paginate($per_page, static::$Vo, 'page', $page);
    }
    public static function row_add(array $attributes): string|BaseModel {           // 批量赋值建议使用create() / 新增或更新updateOrCreate()
        // if (empty($attributes)) return '属性为空';
        $model = new static;
        foreach($attributes as $k=>$v) { $model->$k = $v; }
        try {
            if ($model->save()) {
                return $model;  // attributes + 一个主键
            } else {
                return '新增数据失败';
            }
        } catch(\Exception $e) {
            return $e->getMessage();
        }
    }
    /** 快速选出更新的值 static::when($attrs, $data, 'userName', 'user_name'); */
    // protected static function when(&$attrs, $data, $key, $key2=null){ if(isset($data[$key])) $attrs[$key2??$key] = $data[$key]; }
    public static function row_update(int|array $idOrWhere, array $attributes, $updateOrAdd=false): string|BaseModel {      // 批量更新建议使用eloquent...
        if (empty($idOrWhere) || empty($attributes)) return '条件或属性为空';
        $base_model = new static;
        $where = is_array($idOrWhere) ? $idOrWhere : [[$base_model->primaryKey, '=', $idOrWhere]];
        $model = $base_model->select(static::is_api() ? static::$Voi : static::$Vo)->where($where)->first();   // 先查再改ok
        if (!$model) { 
            if ($updateOrAdd) {
                // 需注意的点, updateOrAdd对where有要求 '='
                foreach(array_filter($where, fn($it)=>$it[1] == '=') as $it){
                    $attributes[$it[0]] = $it[2];
                }
                return static::row_add($attributes);
            }
            return '数据不存在';
        }
        foreach($attributes as $k=>$v) { $_old[$k]=$model->$k; $model->$k = $v; }
        try {
            if ($model->save()) {
                $model->_where = $where;
                $model->_old = $_old;               // 旧数据
                $model->_attrs = $attributes;       // 新数据
                return $model;
            } else {
                return '修改数据失败';
            }
        } catch(\Exception $e) {
            return $e->getMessage();
        }
    }
    public static function row_delete(int|array $idOrWhere): string|BaseModel {     // 批量删除建议使用eloquent...
        if (empty($idOrWhere)) return '条件为空';
        $base_model = new static;
        $where = is_array($idOrWhere) ? $idOrWhere : [[$base_model->primaryKey, '=', $idOrWhere]];
        $model = $base_model->select(static::is_api() ? static::$Voi : static::$Vo)->where($where)->first();   // 先查再删ok
        if (!$model) { return '数据不存在'; }
        try {
            if ($model->delete()) {
                $model->_del = true;         // 删除标识
                return $model;
            } else {
                return '删除数据失败';
            }
        } catch(\Exception $e) {
            return $e->getMessage();
        }
    }

}
